package coreModel;


import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;



import hitExceptions.*;
/**
 * Product is an instance of a single product, it contains a barcode, description, creation date, 
 * deletion date, size, shelfLife
 * @author The Fantastic 4
 *
 */
@SuppressWarnings("serial")
public class Product extends SerializableImplementer implements Comparable <Product>
{


	private Date creationDate;
	private Barcode barcode;
	private String description;
	private Supply productStats;
	private int shelfLife;
	private int threeMonthSupply;

	/**
	 * Creates a product with a barcode, set the creation time to the current time
	 * if you want a differnt creation time you need to call setCreationDate()
	 * @param barcode barcode object that refers to this Product
	 * @throws InvalidOperationException 
	 */
	public Product(String description, Barcode barcode) throws InvalidDataException
	{
		setDescription(description);
		this.barcode=barcode;
		creationDate=new Date();
		productStats=new Supply();

	}

	/**
	 * Each product has a unique barcode so the hash code of that barcode should be unique
	 * so for the products hashcode we use the barcode.hashcode()
	 */
	@Override
	public int hashCode()
	{
		return barcode.hashCode();
	}


	/**
	 * This method is used to test if two objects of same class are equal
	 */
	@Override
	public boolean equals(Object obj) 
	{
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Product i = (Product)obj;
		return barcode.equals(i.getBarcode());
	}


	/**
	 * Constructor where product details are set, note the time is set to the current time
	 * @param barcode barcode obj
	 * @param description  string with name of prod
	 * @param shelfLife integer value for shelf life
	 * @param threeMonthSupply integer value for 3 month supply
	 * @param productStats supply object container size and units
	 * @throws InvalidDataException thrown if invalid data is passes during the creation of
	 * the Product obj
	 * @throws  
	 */
	public Product(Barcode barcode, String description,int shelfLife,
			int threeMonthSupply ,Supply productStats) throws InvalidDataException 
			{
		this.barcode=barcode;
		setDescription(description);
		setSupply(productStats);
		setShelfLife(shelfLife);
		setThreeMonthSupply(threeMonthSupply);
		this.creationDate=new Date();
			}

	/**
	 * gets the date the object was created
	 * @return java.util.date object
	 */
	public Date getCreationDate() 
	{
		return creationDate;
	}

	/**
	 * sets the date the object was created
	 * @param creationDate java.util.date
	 * @throws InvalidDataException 
	 */
	public void setCreationDate(Date creationDate) throws InvalidDataException 
	{
		GregorianCalendar newEntryDate = new GregorianCalendar();
		newEntryDate.setTime(creationDate);
		newEntryDate.set(Calendar.HOUR_OF_DAY, 0);
		newEntryDate.set(Calendar.MINUTE, 0);
		newEntryDate.set(Calendar.SECOND, 0);
		newEntryDate.set(Calendar.MILLISECOND, 0);

		GregorianCalendar earliestDay = new GregorianCalendar();
		earliestDay.clear();
		earliestDay.set(2000, 0, 1, 0, 0, 0);
		//check for before 2000
		if (newEntryDate.compareTo(earliestDay) < 0)
			throw new InvalidDataException("Product creation date is before " +
			"1 Jan 2000. You can't do that!");
		GregorianCalendar today = new GregorianCalendar();
		today.setTime(new Date());

		//check if in the future
		if (newEntryDate.compareTo(today) > 0)
			throw new InvalidDataException("Product creation date is in the future. " +
			"You can't do that!");
		//add date
		this.creationDate = creationDate;
	}
	
	public static boolean isValidDate(Date creationDate)
	{
		if(creationDate==null)
			return false;
		GregorianCalendar newEntryDate = new GregorianCalendar();
		newEntryDate.setTime(creationDate);
		newEntryDate.set(Calendar.HOUR_OF_DAY, 0);
		newEntryDate.set(Calendar.MINUTE, 0);
		newEntryDate.set(Calendar.SECOND, 0);
		newEntryDate.set(Calendar.MILLISECOND, 0);

		GregorianCalendar earliestDay = new GregorianCalendar();
		earliestDay.clear();
		earliestDay.set(2000, 0, 1, 0, 0, 0);
		//check for before 2000
		if (newEntryDate.compareTo(earliestDay) < 0)
			return false;
		
		GregorianCalendar today = new GregorianCalendar();
		today.setTime(new Date());

		//check if in the future
		if (newEntryDate.compareTo(today) > 0)
			return false;
		return true;
	}

	/**
	 * gets the objects barcode
	 * @return the objects barcode
	 */
	public Barcode getBarcode() 
	{
		return barcode;
	}

	/**
	 * sets the barcode for this product
	 * @param barcode string
	 */
	public void setBarcode(Barcode barcode) 
	{
		this.barcode = barcode;
	}



	/**
	 * Gets the object description 
	 * @return string
	 * @throws InvalidOperationException 
	 */
	public String getDescription()
	{

		return description;
	}

	/**
	 * sets the products description
	 * @param description string description
	 */
	public void setDescription(String description)  throws InvalidDataException 
	{
		if(description.length()==0)
		{
			throw new InvalidDataException("A product must have a description");
		}
		this.description = description;
	}

	/**
	 * gets the product objects size
	 * @return float 
	 */
	public Float getSize()
	{
		return productStats.getSupplySize();
	}

	/**
	 * sets the products size, must be a float
	 * @param size product size
	 * @throws InvalidDataException if size is 0 or negative
	 */
	public void setSize(Float size) throws InvalidDataException 
	{
		if (size<=0)
		{
			throw new InvalidDataException("Size must be a positive number");
		}
		if(productStats.getSupplyUnit()==Unit.COUNT && size!=1)
		{
			throw new InvalidDataException("When a product has a count" +
			" it must have a size of 1");
		}
		productStats.setSupplySize( size);
	}

	/**
	 * sets Supply unit type
	 * @param e Unit Enum
	 * @throws InvalidDataException 
	 */
	public void setUnit(Unit e) throws InvalidDataException
	{
		if(e==Unit.COUNT && productStats.getSupplySize()!=1)
		{
			throw new InvalidDataException("When a product has a count" +
			" it must have a size of 1");
		}
		productStats.setSupplyUnit(e);
	}

	/**
	 * Gets the supply type
	 * @return Unit Enum
	 */
	public Unit getUnit()
	{
		return productStats.getSupplyUnit();
	}

	/**
	 * Gets the supply type as a string
	 * @return String
	 */
	public String getUnitAsString()
	{
		if(productStats.getSupplyUnit()==Unit.FLUID_OUNCES)
		{
			return "fluid ounces";
		}
		else
			return productStats.getSupplyUnit().toString().toLowerCase();
		
	}

	/**
	 * gets the objects shelfLife, shelfLife is in months
	 * @return the object's shelf life
	 */
	public int getShelfLife() 
	{
		return shelfLife;
	}

	/**
	 * sets the shelfFile of the object in months
	 * @param shelfLifeNMonths positive int
	 * @throws InvalidDataException when shelf life is negative
	 */
	public void setShelfLife(int shelfLifeNMonths) throws InvalidDataException 
	{
		//make sure it is positive or zero
		if(shelfLifeNMonths<0)
		{
			throw new InvalidDataException("Shelf life must be postive");
		}
		this.shelfLife = shelfLifeNMonths;
	}

	/**
	 * returns the three month supply 
	 * @return An integer, the three month supply
	 */
	public int getThreeMonthSupply() 
	{
		return threeMonthSupply;
	}

	/**
	 * sets the three months supply needed for this product
	 * @param threeMonthSupply
	 * @throws InvalidDataException when three month supply is <0
	 */
	public void setThreeMonthSupply(int threeMonthSupply) throws InvalidDataException 
	{
		if(threeMonthSupply<0)
		{
			throw new InvalidDataException("Three Month Supply must be positive");
		}
		this.threeMonthSupply = threeMonthSupply;
	}

	/**
	 * returns a collections<ProductsContainers> of everywhere this product is located
	 * @return Collection<ProductContainer>
	 */
	public Collection<ProductContainer> getContainers() 
	{
		return HIT.getInstance().getProductManager().getContainersForProduct(this);
	}

	/**
	 * adds a ProdcutContainer to the list of containers that this products is in
	 * @param container a ProductContainer
	 * @throws InvalidOperationException 
	 */
	public void addContainerToProdcutList(ProductContainer container) 
	throws InvalidOperationException
	{
		HIT.getInstance().getProductManager().addProductToContainer(this, container);
	}

	/**
	 * Removes the container for the list of productContainers that this products is in
	 * @param container
	 * @throws InvalidOperationException thrown when container can't be removed
	 */
	public void removeContainerFromProductList(
			ProductContainer container) throws InvalidOperationException
			{
		//checking to see if the container can be removed
		if(!container.canRemoveContainer())
		{
			String msg="Can not remove ";
			msg=msg+container.getName();
			throw new InvalidOperationException(msg);
		}

		HIT.getInstance().getProductManager().removeProductFromContainer(this,container);
			}


	/**
	 * moves all items of this product and this product to destination container
	 * @param origin container
	 * @param destination container
	 * @throws InvalidOperationException 
	 */
	public void moveProduct(ProductContainer origin,
			ProductContainer destination) throws InvalidOperationException
	{
		HIT.getInstance().getProductManager().moveProduct(this,origin,destination);
	}

	/**
	 * returns the Supply also know as the product stats
	 * @return supply obj
	 */
	public Supply getSupply()
	{
		return productStats;
	}


	/**
	 * sets the Supply object
	 * @param productStats supply object
	 * @throws InvalidDataException 
	 */
	public void setSupply(Supply productStats) throws InvalidDataException
	{
		if(productStats.getSupplySize()<=0)
		{
			throw new InvalidDataException("Product Size must be >0");
		}
		if(productStats.getSupplyUnit()==Unit.COUNT && productStats.getSupplySize()!=1)
		{
			throw new InvalidDataException("When a product has a count it must have a size of 1");
		}
		this.productStats=productStats;
	}


	@Override
	public int compareTo(Product p)
	{
		return description.compareTo(p.getDescription());
	}




}
